源码跟踪 在这篇文章中,我将着重详细介绍 springframework(这里使用的版本是 6.2.15)的控制反转(IoC)和 依赖注入(DI)的具体实现,并带你真正手搓一个简化的 spring 容器出来。注解形式的 IoC 容器在元数据解析的实现上与 xml 形式有差异,但在容器运行整体流程上二者基本相同,这里仅以 xml 的视角为例进行说明。
测试 Demo 的构建:
1 2 3 4 5 6 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.2.15</version > </dependency >
业务类:
1 2 3 4 5 @Data public class User { private String id; private String name; }
1 2 3 4 <bean id ="user" class ="com.demo.entity.User" > <property name ="id" value ="1" /> <property name ="name" value ="Owlias" /> </bean >
测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Test void test01 () { ApplicationContext context = new ClassPathXmlApplicationContext ("bean.xml" ); User user = (User) context.getBean("user" ); System.out.println(user); System.out.println(user.getName()); }
源码跟踪时的关键断点:
Spring 6 IoC and DI
Step 0 / 5
Internal Method Stack
// 准备追踪 ClassPathXmlApplicationContext 启动流程...
Class & Implementation
等待启动
点击“下一步”开始分析从 new 对象到 Bean 准备就绪的完整底层调用链。
重置
上一步
下一步
手搓一个 Sping 容器出来 第一阶段:资源与元数据 资源加载模块 (Resource):Spring 的巧妙之处在于它不直接操作 File,而是抽象出了 Resource 接口。这样无论你的 XML 是在类路径、磁盘还是网络,容器的处理逻辑都是一样的。
Resource 接口:
1 2 3 4 5 6 7 public interface Resource { InputStream getInputStream () throws IOException; }
ClassPathResource 实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ClassPathResource implements Resource { private final String path; public ClassPathResource (String path) { this .path = path; } @Override public InputStream getInputStream () throws IOException { InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); if (is == null ) { throw new FileNotFoundException (path + " cannot be opened because it does not exist" ); } return is; } }
有了资源读取能力,我们需要定义一个类来存储解析出来的 XML 信息。这就是 BeanDefinition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class BeanDefinition { @Getter private String beanClassName; @Getter private List<PropertyValue> propertyValues = new ArrayList <>(); public BeanDefinition (String beanClassName) { this .beanClassName = beanClassName; } public void addPropertyValue (PropertyValue pv) { this .propertyValues.add(pv); } }
“画像” 的属性单元 PropertyValue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class PropertyValue { private final String name; private final Object value; public PropertyValue (String name, Object value) { this .name = name; this .value = value; } public String getName () { return name; } public Object getValue () { return value; } }
有了Bean的 “画像”,我们需要一个能存放 “画像” 的地方。Spring 设计了 BeanDefinitionRegistry 接口,让工厂具备注册画像的能力。
1 2 3 4 5 6 7 public interface BeanDefinitionRegistry { void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) ; }
第一阶段的灵魂组件 BeanDefinitionReader 。在 Spring 中,它的职责是拿着刚刚我们写的 Resource,通过 DOM 解析 XML 标签,把 id 和 class 提取出来,封装成 BeanDefinition,最后塞进工厂的 beanDefinitionMap 里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.NodeList;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import java.io.InputStream;public class XmlBeanDefinitionReader { private final BeanDefinitionRegistry registry; public XmlBeanDefinitionReader (BeanDefinitionRegistry registry) { this .registry = registry; } public void loadBeanDefinitions (Resource resource) throws Exception { try (InputStream inputStream = resource.getInputStream()) { doLoadBeanDefinitions(inputStream); } } protected void doLoadBeanDefinitions (InputStream inputStream) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(inputStream); Element root = doc.getDocumentElement(); NodeList nodes = root.getChildNodes(); for (int i = 0 ; i < nodes.getLength(); i++) { if (!(nodes.item(i) instanceof Element)) continue ; Element ele = (Element) nodes.item(i); if (!"bean" .equals(ele.getNodeName())) continue ; String id = ele.getAttribute("id" ); String className = ele.getAttribute("class" ); BeanDefinition beanDefinition = new BeanDefinition (className); NodeList propNodes = ele.getElementsByTagName("property" ); for (int j = 0 ; j < propNodes.getLength(); j++) { Element propEle = (Element) propNodes.item(j); String name = propEle.getAttribute("name" ); String value = propEle.getAttribute("value" ); beanDefinition.addPropertyValue(new PropertyValue (name, value)); } registry.registerBeanDefinition(id, beanDefinition); } } }
最后实现一个最简单的工厂 (BeanFactory):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DefaultListableBeanFactory implements BeanDefinitionRegistry { private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap <>(); @Override public void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) { beanDefinitionMap.put(beanName, beanDefinition); } public BeanDefinition getBeanDefinition (String beanName) { return beanDefinitionMap.get(beanName); } }
现在,我们可以真正通过读取 XML 来生成画像了!来,简单测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void test01 () throws Exception { DefaultListableBeanFactory factory = new DefaultListableBeanFactory (); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader (factory); Resource resource = new ClassPathResource ("bean.xml" ); reader.loadBeanDefinitions(resource); BeanDefinition bd = factory.getBeanDefinition("user" ); System.out.println("成功加载 Bean: " + bd.getBeanClassName()); PropertyValue pv0 = bd.getPropertyValues().get(0 ); System.out.println("属性注入检测: " + pv0.getName() + " = " + pv0.getValue()); PropertyValue pv1 = bd.getPropertyValues().get(1 ); System.out.println("属性注入检测: " + pv1.getName() + " = " + pv1.getValue()); }
测试结果:
1 2 3 成功加载 Bean: com.demo.entity.User 属性注入检测: id = 1 属性注入检测: name = Owlias
第二阶段:实现控制反转 在第一阶段,我们已经把 “设计图纸”(BeanDefinition)塞进了 beanDefinitionMap。现在我们要打造那台根据图纸生产零件的 “机器”——getBean 流程 。
按照 Spring 的设计,我们要通过模板模式来组织代码:
BeanFactory: 顶层接口,定义 getBean 协议。
AbstractBeanFactory: 抽象类,处理单例缓存逻辑(一级缓存)。
AbstractAutowireCapableBeanFactory: 负责具体的实例化(反射 new 对象)和初始化。
DefaultListableBeanFactory:落地实现类,它继承了上面的所有生产能力,并且实现了 BeanDefinitionRegistry。
下面我们来具体实现这一阶段的主要逻辑:
BeanFactory:
1 2 3 4 5 6 7 public interface BeanFactory { Object getBean (String name) throws Exception; }
AbstractBeanFactory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public abstract class AbstractBeanFactory implements BeanFactory { private final Map<String, Object> singletonObjects = new ConcurrentHashMap <>(256 ); @Override public Object getBean (String name) throws Exception { Object bean = singletonObjects.get(name); if (bean != null ) { return bean; } BeanDefinition beanDefinition = getBeanDefinition(name); if (beanDefinition == null ) { throw new Exception ("No bean named " + name + " is defined" ); } return createBean(name, beanDefinition); } protected void addSingleton (String beanName, Object singletonObject) { singletonObjects.put(beanName, singletonObject); } protected abstract BeanDefinition getBeanDefinition (String beanName) ; protected abstract Object createBean (String beanName, BeanDefinition beanDefinition) throws Exception; }
AbstractAutowireCapableBeanFactory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory { @Override protected Object createBean (String beanName, BeanDefinition beanDefinition) throws Exception { Object bean; try { bean = createBeanInstance(beanDefinition); applyPropertyValues(beanName, bean, beanDefinition); } catch (Exception e) { throw new RuntimeException ("Instantiation of bean failed" , e); } addSingleton(beanName, bean); return bean; } protected Object createBeanInstance (BeanDefinition beanDefinition) throws Exception { Class<?> beanClass = Class.forName(beanDefinition.getBeanClassName()); return beanClass.getDeclaredConstructor().newInstance(); } protected void applyPropertyValues (String beanName, Object bean, BeanDefinition beanDefinition) { } }
DefaultListableBeanFactory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry { private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap <>(); @Override public void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) { beanDefinitionMap.put(beanName, beanDefinition); } public BeanDefinition getBeanDefinition (String beanName) { return beanDefinitionMap.get(beanName); } }
通过第二阶段,现在我们就能够搓出一个由工厂创建的真正的 user 了!来,测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void test03 () throws Exception { DefaultListableBeanFactory factory = new DefaultListableBeanFactory (); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader (factory); Resource resource = new ClassPathResource ("bean.xml" ); reader.loadBeanDefinitions(resource); User user = (User) factory.getBean("user" ); System.out.println(user); System.out.println(user.getClass() + "@" + Integer.toHexString(System.identityHashCode(user))); user.sayHello(); }
测试结果:
1 2 3 User(id=null, name=null) class com.demo.entity.User@6dc17b83 hello world
第三阶段:实现依赖注入 在第二阶段,我们已经通过反射 new 出了对象,但它只是一个 “空壳”。现在我们要实现 applyPropertyValues 方法,利用反射把配置中的属性值强行灌输给这个空壳,让它变成一个具备业务数据的 “完全体”。
在 Spring 源码中,这个过程是由 BeanWrapper 来配合完成的,但在我们的简易版实现中,我们将通过简单的反射对 Setter 方法进行调用。
现在,我们就实现之前 AbstractAutowireCapableBeanFactory#applyPropertyValues 的坑位:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory { protected void applyPropertyValues (String beanName, Object bean, BeanDefinition beanDefinition) throws Exception { try { for (PropertyValue pv : beanDefinition.getPropertyValues()) { String name = pv.getName(); Object value = pv.getValue(); String setterMethodName = "set" + name.substring(0 , 1 ).toUpperCase() + name.substring(1 ); Class<?> clazz = bean.getClass(); Method method = clazz.getDeclaredMethod(setterMethodName, value.getClass()); method.setAccessible(true ); method.invoke(bean, value); } } catch (Exception e) { throw new Exception ("Error setting property values for bean: " + beanName, e); } } }
现在再来运行第二阶段的测试单元:
1 2 3 User(id=1, name=Owlias) class com.demo.entity.User@1283bb96 hello world
DI 注入,大功告成!
让使用更优雅 你可能注意到咱们的测试单元调用似乎很繁琐,不像spring那样优雅,我们现在就来写一个精简版的 ApplicationContext,让它实现 “一行代码启动容器并自动注入” 的壮举!
在 Spring 源码设计中,BeanFactory 是底层容器,面向 Spring 框架本身;而 ApplicationContext 是高层容器,面向的是开发者。ApplicationContext 的核心作用是:把 Resource 加载、BeanDefinition 解析、BeanFactory 注册这几项琐碎的工作包装起来,让你实现 “一行代码启动 Spring 容器”。下面我将遵循 Spring 6 的继承体系,来实现一个最经典的 ClassPathXmlApplicationContext。
顶层接口 ApplicationContext:
1 2 3 4 5 public interface ApplicationContext extends BeanFactory {}
抽象基类 AbstractApplicationContext:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public abstract class AbstractApplicationContext implements ApplicationContext { protected DefaultListableBeanFactory beanFactory; @Override public Object getBean (String name) throws Exception { return beanFactory.getBean(name); } public void refresh () throws Exception { refreshBeanFactory(); } protected abstract void refreshBeanFactory () throws Exception; }
真正干活的 XML 实现类 ClassPathXmlApplicationContext:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ClassPathXmlApplicationContext extends AbstractApplicationContext { private String configLocation; public ClassPathXmlApplicationContext (String configLocation) throws Exception { this .configLocation = configLocation; super .refresh(); } @Override protected void refreshBeanFactory () throws Exception { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory (); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader (beanFactory); reader.loadBeanDefinitions(new ClassPathResource (configLocation)); this .beanFactory = beanFactory; } }
好,现在再来看我们的测试单元,是不是看起来有点像真正的 spring 容器了呢:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testMyApplicationContext () throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext ("bean.xml" ); User user = (User) context.getBean("user" ); user.sayHello(); }
标题:
Spring IoC 和 DI 的简单实现